package com.huawei.admins.platform.gateway.core.component.predicate.factory;

import com.huawei.admins.platform.gateway.core.component.predicate.AsyncPredicate;
import com.huawei.admins.platform.gateway.core.component.predicate.PredicateDefinition;
import com.huawei.admins.platform.gateway.core.config.GatewayProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.server.PathContainer;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.function.Predicate;

import static com.huawei.admins.platform.gateway.core.util.ServerWebExchangeUtils.*;
import static org.springframework.http.server.PathContainer.parsePath;

public class PathRoutePredicateFactory implements RoutePredicateFactory {

    private static final Log logger = LogFactory.getLog(PathRoutePredicateFactory.class);

    private PathPatternParser pathPatternParser = new PathPatternParser();

    public PathRoutePredicateFactory() {
        this.pathPatternParser = new PathPatternParser();
        this.pathPatternParser.setMatchOptionalTrailingSeparator(true);
    }

    public void setPathPatternParser(PathPatternParser pathPatternParser) {
        this.pathPatternParser = pathPatternParser;
    }

    public PathPatternParser getPathPatternParser() {
        return pathPatternParser;
    }

    private static void traceMatch(String prefix, Object desired, Object actual, boolean match) {
        if (logger.isTraceEnabled()) {
            String message = String.format("%s \"%s\" %s against value \"%s\"", prefix, desired,
                    match ? "matches" : "does not match", actual);
            logger.trace(message);
        }
    }

    @Override
    public Predicate<ServerWebExchange> apply(PredicateDefinition definition, GatewayProperties properties) {

        ArrayList<PathPattern> pathPatterns = new ArrayList<>();
        Collection<String> patterns = definition.getArgs().values();
        patterns.forEach(pattern -> {
            PathPattern pathPattern = this.pathPatternParser.parse(pattern);
            pathPatterns.add(pathPattern);
        });

        return new Predicate<>() {

            @Override
            public boolean test(ServerWebExchange exchange) {
                PathContainer path = (PathContainer) exchange.getAttributes().computeIfAbsent(
                        GATEWAY_PREDICATE_PATH_CONTAINER_ATTR,
                        s -> parsePath(exchange.getRequest().getURI().getRawPath()));

                PathPattern match = null;
                for (PathPattern pathPattern : pathPatterns) {
                    if (pathPattern.matches(path)) {
                        match = pathPattern;
                        break;
                    }
                }

                if (match != null) {
                    traceMatch("Pattern", match.getPatternString(), path, true);
                    exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ATTR, match.getPatternString());
                    String routeId = (String) exchange.getAttributes().get(GATEWAY_PREDICATE_ROUTE_ATTR);
                    if (routeId != null) {
                        exchange.getAttributes().put(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR, routeId);
                    }
                    return true;
                } else {
                    traceMatch("Pattern", patterns, path, false);
                    return false;
                }
            }

            @Override
            public String toString() {
                return String.format("Paths: %s, match trailing slash: %b", patterns, true);
            }
        };
    }
}
